<template>
  <div class="app-container">
    <el-container>
      <el-header style="box-shadow: 0 1px 3px 0 rgb(0, 0, 0, 0.1)">
        <div class="Grid Grid--justifyEnd Grid--justifyCenter">
          <div>
            <el-button type="primary" @click="handleShowMockData">查看保存数据</el-button>
            <!-- @click="handlePreview" -->
            <el-button type="primary">预览</el-button>
            <el-button type="primary" @click="handleSubmit">保存</el-button>
          </div>
        </div>
      </el-header>
      <el-container>
        <el-aside width="400px" style="padding: 0 10px">
          <!-- @tab-click="handleClick" -->
          <el-tabs v-model="typeActive" class="demo-tabs">
            <el-tab-pane label="画布配置" name="config">
              <div>
                <div class="Grid Grid--center" style="color: rgb(78, 89, 105)">
                  <div style="padding: 0 10px">宽度</div>
                  <div>
                    <el-input
                      v-model="storeCanvasProps.width"
                      type="number"
                      disabled
                      style="width: 120px"
                      placeholder="画布宽度"
                    />
                  </div>
                  <div style="padding: 0 10px">高度</div>
                  <div>
                    <el-input
                      v-model="storeCanvasProps.height"
                      type="number"
                      disabled
                      style="width: 120px"
                      placeholder="画布高度"
                    />
                  </div>
                </div>
              </div>
            </el-tab-pane>
            <el-tab-pane label="模板" name="template">
              <div></div>
            </el-tab-pane>
            <el-tab-pane label="元素" name="element">
              <div
                class="Grid Grid--1of3 Grid--center"
                v-for="(item, index) in elementList"
                :key="index"
                style="margin-bottom: 20px"
              >
                <div v-for="e in item" :key="e.id" class="Grid-cell">
                  <div class="Grid Grid--center Grid--justifyCenter">
                    <el-button :type="primary" text bg @click="handleAddElement(e)">
                      {{ e.value }}
                    </el-button>
                  </div>
                </div>
              </div>
            </el-tab-pane>
            <el-tab-pane label="素材" name="widget">
              <div></div>
            </el-tab-pane>
          </el-tabs>
        </el-aside>
        <el-container>
          <el-main
            ref="el"
            style="padding: 0; height: calc(100vh - 274px); background-color: rgb(241, 241, 241, 1)"
          >
            <div>
              <canvas ref="CanvasRef"></canvas>
              <!-- <canvas ref="test"></canvas> -->
            </div>
          </el-main>
          <el-footer style="background-color: rgb(241, 241, 241, 1)">
            <el-divider>v1.0.2</el-divider>
          </el-footer>
        </el-container>
      </el-container>
    </el-container>
  </div>
</template>

<script>
import { fabric } from 'fabric'
import { useElementSize } from '@vueuse/core'
// import _bg from 'https://liaoma-test.oss-cn-shenzhen.aliyuncs.com/liaoma/admin/assets/fabric/bg.png'
// import _scanCode from '@/assets/fabric/scanCode.png'
// import _avatar from '@/assets/fabric/avatar.png'

const QR_CODE_IMAGE_URL = 'https://liaoma-test.oss-cn-shenzhen.aliyuncs.com/liaoma/admin/assets/fabric/scanCode.png'

const AVATAR_URL = 'https://liaoma-test.oss-cn-shenzhen.aliyuncs.com/liaoma/admin/assets/fabric/avatar.png'

const WORKSPACE_ID = 'workspace'

const BACKGROUND_ID = 'background'

const DEFAULT_TEXT_OPTIONS = {
  fontSize: 14,
  hasControls: true,
  lockScalingX: false,
  lockScalingY: false,
  centeredScaling: true,
  lockRotation: false, // 锁定旋转
  originX: 'left',
  originY: 'bottom'
}

const DEFAULT_IMAGE_OPTIONS = {
  hasControls: true,
  lockScalingFlip: true,
  lockRotation: false, // 锁定旋转
  originX: 'left',
  originY: 'bottom'
}
/**
 * 预览图片配置
 */
const PREVIEW_URL_OPTIONS = {
  format: 'png',
  quality: 0.8,
  multiplier: 1
}

/**
 * 预设可以添加模板的字段
 */
const ELEMENT_LIST = [
  {
    type: 'text',
    value: '{{方案标题}}',
    id: 'title'
  },
  {
    type: 'text',
    value: '{{方案价格}}',
    id: 'price'
  },
  {
    type: 'text',
    value: '{{销售昵称}}',
    id: 'nickname'
  },
  {
    type: 'text',
    value: '{{赛事标题}}',
    id: 'match_title'
  },
  {
    type: 'text',
    value: '{{主队 vs 客队}}',
    id: 'team_info'
  },
  {
    type: 'text',
    value: '{{赛事时间}}',
    id: 'team_datetime'
  },
  {
    type: 'image',
    value: '{{销售头像}}',
    id: 'avatar_url'
  },
  {
    type: 'image',
    value: '{{二维码}}',
    id: 'qr_code'
  }
]

export default {
  name: 'Fabric',
  components: {},

  data() {
    return {
      ELEMENT_LIST,
      // 数据
      tempFormData: {},
      // 类型
      typeActive: 'config',
      // 工作区
      wrapBox: {
        wrapWidth: useElementSize(this.$refs.el).width.value,
        wrapHeight: useElementSize(this.$refs.el).height.value
      },
      isLoad: false,
      // 画布实例
      storeCanvas: shallowRef(),
      // storeCanvasTest: shallowRef(),
      // 画布属性
      storeCanvasProps: {
        width: 360,
        height: 530
      },
      // 背景图片
      storeCanvasBackgroundImage: shallowRef(),
      // idle processing zooming
      storeCanvasStatus: 'idle',
      storeImageLoading: false,
      elementList: []
    }
  },
  watch: {
    // 监听容器宽高变化
    wrapBox: {
      handler(newValue, oldValue) {
        if (newValue.wrapWidth && newValue.wrapHeight && this.storeCanvas) {
          if (!this.isLoad) {
            this.canvasInitWorkspace(this.storeCanvas, newValue.wrapWidth, newValue.wrapHeight)

            this.isLoad = true

            this.canvasWorkspaceAutoZoom(this.storeCanvas, newValue.wrapWidth, newValue.wrapHeight)
          }
        }
      },
      deep: true,
      debounce: 500,
      maxWait: 1000
    },
    // 监听画布属性
    storeCanvasProps: {
      handler(newValue, oldValue) {
        if (this.wrapBox.wrapWidth && this.wrapBox.wrapHeight && this.storeCanvas) {
          this.canvasWorkspaceAutoZoom(
            this.storeCanvas,
            this.wrapBox.wrapWidth,
            this.wrapBox.wrapHeight
          )
        }
      },
      deep: true,
      debounce: 500,
      maxWait: 1000
    },
    // 手动自动缩放
    storeCanvasStatus: {
      handler(newValue, oldValue) {
        console.warn('[watchDebounced] 手动自动缩放', this.storeCanvasStatus)
        if (this.storeCanvasStatus === 'zooming' && this.storeCanvas) {
          this.storeCanvasStatus = 'processing'
          this.canvasWorkspaceAutoZoom(
            this.storeCanvas,
            this.wrapBox.wrapWidth,
            this.wrapBox.wrapHeight
          )
          this.storeCanvasStatus = 'idle'
        }
      },
      deep: true,
      debounce: 500,
      maxWait: 1000
    }
  },
  created() {},
  mounted() {
    // 初始化
    this.loadInit()
    this.$nextTick(() => {
      const el = this.$refs.el
      this.wrapBox.wrapWidth = useElementSize(el).width.value
      this.wrapBox.wrapHeight = useElementSize(el).height.value

      // hooks
      const value = new fabric.Canvas(this.$refs.CanvasRef, {
        preserveObjectStacking: true,
        fireRightClick: true, // 启用右键，button的数字为3
        stopContextMenu: true, // 禁止默认右键菜单
        controlsAboveOverlay: true // 超出clipPath后仍然展示控制条
      })
      this.storeCanvas = value
      setTimeout(() => {
        this.canvasSetBackgroundByUrl()
        this.canvasAddElement(ELEMENT_LIST[0])
      }, 1000)

      // test
      // this.storeCanvasTest = new fabric.Canvas(this.$refs.test, {
      //   preserveObjectStacking: true,
      //   fireRightClick: true, // 启用右键，button的数字为3
      //   stopContextMenu: true, // 禁止默认右键菜单
      //   controlsAboveOverlay: true // 超出clipPath后仍然展示控制条
      // })
      // var rect = new fabric.Rect({
      //   left: 100, //距离画布左侧的距离，单位是像素
      //   top: 100, //距离画布上边的距离
      //   fill: 'red', //填充的颜色
      //   width: 30, //方形的宽度
      //   height: 30 //方形的高度
      // })
      // this.storeCanvasTest.add(rect)
    })
  },
  methods: {
    // 初始化
    loadInit() {
      let result = []
      for (var i = 0; i < ELEMENT_LIST.length; i += 3) {
        result.push(ELEMENT_LIST.slice(i, i + 3))
      }
      this.elementList = result
    },
    // 初始画布
    canvasInitWorkspace(canvas, wrapWidth, wrapHeight) {
      console.warn('[canvas.ts]:', 'canvasInitWorkspace')
      this._initBackground(canvas)
      this._autoSetCanvasWidthHeight(canvas, wrapWidth, wrapHeight)
      // 添加删除图标
      this._deleteControl(canvas)
      // 初始化画布
      this._initWorkspace(canvas)
    },
    // 无背景图片
    _initBackground(canvas) {
      canvas.backgroundImage = ''
    },
    // 置画布canvas动态大小
    _autoSetCanvasWidthHeight(canvas, wrapWidth, wrapHeight) {
      canvas.setWidth(wrapWidth)
      canvas.setHeight(wrapHeight)
    },
    _drawImg(ctx, left, top, img, wSize, hSize, angle) {
      if (angle === undefined) return
      ctx.save()
      ctx.translate(left, top)
      ctx.rotate(fabric.util.degreesToRadians(angle))
      ctx.drawImage(img, -wSize / 2, -hSize / 2, wSize, hSize)
      ctx.restore()
    },
    // 删除
    _deleteControl(delCanvas) {
      const deleteIcon =
        "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' id='Ebene_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='595.275px' height='595.275px' viewBox='200 215 230 470' xml:space='preserve'%3E%3Ccircle style='fill:%23F44336;' cx='299.76' cy='439.067' r='218.516'/%3E%3Cg%3E%3Crect x='267.162' y='307.978' transform='matrix(0.7071 -0.7071 0.7071 0.7071 -222.6202 340.6915)' style='fill:white;' width='65.545' height='262.18'/%3E%3Crect x='266.988' y='308.153' transform='matrix(0.7071 0.7071 -0.7071 0.7071 398.3889 -83.3116)' style='fill:white;' width='65.544' height='262.179'/%3E%3C/g%3E%3C/svg%3E"
      const delImg = document.createElement('img')
      delImg.src = deleteIcon
      // 删除图标
      fabric.Object.prototype.controls.deleteControl = new fabric.Control({
        x: 0.5,
        y: -0.5,
        offsetY: -16,
        offsetX: 16,
        cursorStyle: 'pointer',
        // 删除选中元素
        mouseUpHandler: (mouseEvent, target) => {
          console.log(target)
          if (target.action === 'rotate') return true
          const activeObject = delCanvas.getActiveObjects()
          if (activeObject) {
            activeObject.map((item) => delCanvas.remove(item))
            delCanvas.requestRenderAll()
            delCanvas.discardActiveObject()
          }
          // triggerRef(this.storeCanvas)
          return true
        },
        render: (ctx, left, top, styleOverride, fabricObject) => {
          this._drawImg(ctx, left, top, delImg, 24, 24, fabricObject.angle)
        }
        // cornerSize: 24,
      })
    },
    // 在canvas中创建workspace白色矩形
    _initWorkspace(canvas) {
      console.log(canvas.width)
      const workspace = new fabric.Rect({
        fill: 'rgba(255,255,255,1)',
        width: this.storeCanvasProps.width,
        height: this.storeCanvasProps.height,
        left: (canvas.width - this.storeCanvasProps.width) / 2,
        top: (canvas.height - this.storeCanvasProps.height) / 2,
        id: WORKSPACE_ID
      })
      workspace.set('selectable', false)
      workspace.set('hasControls', false)
      workspace.hoverCursor = 'default'
      canvas.add(workspace)
    },
    // 画布自动缩放
    canvasWorkspaceAutoZoom(canvas, wrapWidth, wrapHeight) {
      console.warn('[canvas.ts]:', 'canvasWorkspaceAutoZoom')
      const scale = this._getWorkspaceScale(wrapWidth, wrapHeight)
      console.log(scale)
      this._setWorkspaceZoom(canvas, scale - 0.1, wrapWidth, wrapHeight)
    },
    // 获得缩放比例
    _getWorkspaceScale(wrapWidth, wrapHeight) {
      // 按照宽度
      if (wrapWidth / wrapHeight < this.storeCanvasProps.width / this.storeCanvasProps.height)
        return wrapWidth / this.storeCanvasProps.width
      // 按照宽度缩放
      return wrapHeight / this.storeCanvasProps.height
    },
    // 设置缩放
    _setWorkspaceZoom(canvas, scale, wrapWidth, wrapHeight) {
      this._autoSetCanvasWidthHeight(canvas, wrapWidth, wrapHeight)
      const center = canvas.getCenter()
      canvas.setViewportTransform(fabric.iMatrix.concat())
      canvas.zoomToPoint(new fabric.Point(center.left, center.top), scale)
      // 居中画布
      const workspace = canvas.getObjects().find((item) => item.id === WORKSPACE_ID)
      console.log(workspace)
      if (!workspace) return
      this._setWorkspaceSize(workspace)
      this._setCenterFromObject(canvas, workspace)
      // 超出画布不展示
      workspace.clone((cloned) => {
        canvas.clipPath = cloned
        canvas.requestRenderAll()
      })
    },
    _setWorkspaceSize(workspace) {
      workspace.set('width', this.storeCanvasProps.width)
      workspace.set('height', this.storeCanvasProps.height)
    },
    // 设置画布中心到指定对象中心点上
    _setCenterFromObject(canvas, obj) {
      const objCenter = obj.getCenterPoint()
      const viewportTransform = canvas.viewportTransform
      if (canvas.width === undefined || canvas.height === undefined || !viewportTransform) return
      console.log('canvas.width:', canvas.width, 'canvas.height:', canvas.height)
      console.log(objCenter)
      viewportTransform[4] = canvas.width / 2 - objCenter.x * viewportTransform[0]
      viewportTransform[5] = canvas.height / 2 - objCenter.y * viewportTransform[3]
      console.log(viewportTransform)
      // [0]: 水平缩放（x轴方向）
      // [1]: 水平倾斜（x轴方向）
      // [2]: 垂直倾斜（y轴方向）
      // [3]: 垂直缩放（y轴方向）
      // [4]: 水平移动（x轴方向）
      // [5]: 垂直移动（y轴方向）
      // canvas.setViewportTransform([1, 0, 0, 1, 200, 0])
      console.log(viewportTransform[4], viewportTransform[5])
      canvas.setViewportTransform(viewportTransform)
      canvas.renderAll()
    },
    canvasSetBackgroundByUrl() {
      fabric.Image.fromURL('https://liaoma-test.oss-cn-shenzhen.aliyuncs.com/liaoma/admin/assets/fabric/bg.png', (imgInstance) => {
        console.log(imgInstance)
        const res = this._existWorkspace()
        console.log(res)
        if (res) {
          const { canvas, workspace } = res
          // 填充背景
          // 计算矩形和图片的宽高比
          const rectAspectRatio = workspace / workspace.height
          const imgAspectRatio = imgInstance.width / imgInstance.height

          // 根据宽高比调整图片尺寸和位置
          if (imgAspectRatio > rectAspectRatio) {
            // 图片的宽高比较大，以矩形的高度为基准进行缩放
            imgInstance.scaleToHeight(workspace.height)
          } else {
            // 图片的宽高比较小，以矩形的宽度为基准进行缩放
            imgInstance.scaleToWidth(workspace.width)
          }

          imgInstance.set({
            id: BACKGROUND_ID,
            name: '背景图片',
            left: workspace.left,
            top: workspace.top
          })
          // 移除之前的背景图片
          if (this.storeCanvasBackgroundImage) canvas.remove(this.storeCanvasBackgroundImage)

          canvas.add(imgInstance)
          // 先放置在最底层
          canvas.sendToBack(imgInstance)
          // 再向上移动一层
          canvas.bringForward(imgInstance, false)
          // 更新背景图片变量
          this.storeCanvasBackgroundImage = imgInstance
          canvas.renderAll()
        }
      })
    },
    // 是否存在画布
    _existCanvas() {
      if (!this.storeCanvas) alert('没有找到画布')

      return this.storeCanvas
    },
    // 是否存在工作区
    _existWorkspace() {
      const canvas = this._existCanvas()
      if (canvas) {
        const workspace = canvas.getObjects().find((item) => item.id === WORKSPACE_ID)
        if (workspace) return { workspace, canvas }
      }
      alert('没有找到工作区')
    },
    handleAddElement(item) {
      this.canvasAddElement(item)
    },
    canvasAddElement(record) {
      console.log('DEFAULT_TEXT_OPTIONS', DEFAULT_TEXT_OPTIONS)
      const res = this._existWorkspace()
      if (res) {
        const { canvas, workspace } = res
        if (record.type === 'text') {
          const text = new fabric.Text(record.value, {
            id: record.id,
            fontWeight: 'normal',
            name: record.value,
            left: workspace.left + (record.left ?? workspace.width / 2), // left
            top: workspace.top + (record.top ?? workspace.height / 2), // bottom
            scaleX: record.scaleX ?? 1,
            scaleY: record.scaleY ?? 1,
            ...DEFAULT_TEXT_OPTIONS
          })
          canvas.add(text)
          canvas.setActiveObject(text)
          canvas.renderAll()
          // triggerRef(this.storeCanvas)
        }
        if (record.type === 'image') {
          this.storeImageLoading = true
          let _url = ''
          if (record.id === 'qr_code') {
            _url = QR_CODE_IMAGE_URL
          } else if (record.id === 'avatar_url') {
            _url = AVATAR_URL
          }
          fabric.Image.fromURL(_url, (imgInstance) => {
            canvas.add(
              imgInstance.set({
                left: workspace.left + (record.left ?? workspace.width / 2), // left
                top: workspace.top + (record.top ?? workspace.height / 2), // bottom
                scaleX: record.scaleX ?? 1,
                scaleY: record.scaleY ?? 1,
                id: record.id,
                name: record.value,
                ...DEFAULT_IMAGE_OPTIONS
              })
            )
            canvas.setActiveObject(imgInstance)
            canvas.renderAll()
            this.storeImageLoading = false
            // triggerRef(this.storeCanvas)
          })
        }
      }
    },
    // 查看数据
    handleShowMockData() {
      console.log(JSON.stringify(this.tempFormData))
    },
    // 保存
    handleSubmit() {
      const res = this.canvasSpliceElementListAndBackground()
      if (res) {
        console.warn('[handleSubmit]:', res)
        this.tempFormData = res
      }
    },
    canvasSpliceElementListAndBackground() {
      const res = this._existWorkspace()
      if (res) {
        const { canvas, workspace } = res
        const workspaceLeft = workspace.left
        const workspaceTop = workspace.top
        const elementList = []

        canvas.getObjects().forEach((item) => {
          if (item.id !== WORKSPACE_ID && item.id !== BACKGROUND_ID) {
            item.visible = false
            elementList.push({
              id: item.id,
              value: item.name,
              left: item.left - workspaceLeft,
              top: item.top - workspaceTop,
              scaleX: item.scaleX,
              scaleY: item.scaleY,
              angle: item.angle ?? 0,
              type: item.type
            })
          }
        })
        console.warn('[canvasSpliceElementListAndBackground]:', elementList)
        canvas.setViewportTransform([1, 0, 0, 1, 0, 0])
        canvas.renderAll()
        const backgroundImageBase64 = this.canvasToImageUrl() ?? ''
        console.log('backgroundImageBase64:', backgroundImageBase64)
        canvas.getObjects().forEach((item) => {
          if (item.id !== WORKSPACE_ID && item.id !== BACKGROUND_ID) item.visible = true
        })
        this.storeCanvasStatus = 'zooming'
        return {
          elementList,
          backgroundImageBase64,
          width: this.storeCanvasProps.width,
          height: this.storeCanvasProps.height
        }
      }
    },
    // 将当前画布中的工作区转为图片链接
    canvasToImageUrl() {
      const res = this._existWorkspace()
      if (res) {
        const { canvas, workspace } = res
        const { width, height, left, top } = workspace
        const options = {
          ...PREVIEW_URL_OPTIONS,
          width,
          height,
          left,
          top
        }
        canvas.setViewportTransform([1, 0, 0, 1, 0, 0])
        canvas.renderAll()
        const url = canvas.toDataURL(options)
        this.storeCanvasStatus = 'zooming'
        return url
      }
    },
    // 将数据覆盖到当前画布中
    handleSetData() {
      this.storeCanvasProps.height = this.tempFormData.height
      this.storeCanvasProps.width = this.tempFormData.width
      const canvasElementList = this.tempFormData.elementList
      const canvasBackgroundImageBase64 = this.tempFormData.backgroundImageBase64
      // 1. 已经自动设置了画布的宽高
      // 2. 手动设置背景图片
      if (canvasBackgroundImageBase64) {
        const url = this._base64ToBlobUrl(canvasBackgroundImageBase64)
        this.canvasSetBackgroundByUrl(url)
      }
      // 3. 设置二维码图片,先将图片排序到最前面
      const sortElementList = canvasElementList.sort((x, y) => (x.type > y.type ? 1 : -1))
      console.warn('[handleSetData]:', sortElementList)
      sortElementList.forEach((element) => {
        console.warn(element)
        this.canvasAddElement(element)
      })
    },
    // base64转为Url
    _base64ToBlobUrl(base64) {
      // 删除数据类型和编码信息
      const base64WithoutInfo = base64.split(',')[1]

      // 解码 base64 数据
      const decodedData = atob(base64WithoutInfo)

      // 将解码后的数据转换为 Uint8Array
      const dataArray = new Uint8Array(decodedData.length)
      for (let i = 0; i < decodedData.length; i++) dataArray[i] = decodedData.charCodeAt(i)

      // 创建 Blob 对象
      const blob = new Blob([dataArray], { type: 'application/octet-stream' })

      // 创建 Blob URL
      const blobUrl = URL.createObjectURL(blob)

      return blobUrl
    }
  }
}
</script>

<style lang="scss" scoped>
::v-deep .el-divider__text {
  background-color: rgb(241, 241, 241, 1);
}
</style>
